﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    using Phuse.UnitTesting;

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /// <summary>Tests the <see cref="Visitable{T, U}"/>, <see cref="VisitableHelper"/> and classes.</summary>
    [TestClass]
    public sealed class VisitorTest : TestBase
    {
        private const int MaxElementCount = 20;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Tests the <see cref="Visitable{T, U}"/> and <see cref="VisitableHelper"/> classes.</summary>
        [TestMethod]
        public void VisitableTest()
        {
            object obj = new object();
            int whatever = obj.GetHashCode();

            IMyVisitable[] visitables =
                new IMyVisitable[] { new MyVisitable1(), new MyVisitable2(), new MyVisitable3() };
            MyVisitor visitor = new MyVisitor();

            foreach (IMyVisitable visitable in visitables)
            {
                Visit(visitable, visitor);
                Assert.AreEqual(1, visitable.Visited);
            }

            VisitableHelper.Accept(new MyVisitable1(), visitor);
            VisitableHelper.Accept(new MyVisitable2(), (IMyVisitor<MyVisitable2>)visitor);

            AssertThrow<InvalidCastException>(() => Visit(new MyBadVisitable(), visitor));
            AssertThrow<ArgumentNullException>(() => Visit(new MyVisitable2(), null));
            AssertThrow<ArgumentNullException>(
                () => VisitableHelper.Accept<MyVisitable1, IMyVisitable>(null, visitor));
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private static void Visit(IMyVisitable visitable, IMyCombinedVisitor visitor)
        {
            visitable.Accept(visitor);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Represents the methods used for testing <see cref="IVisitable{T}"/>.</summary>
        private interface IMyVisitable : IVisitable<IMyVisitable>
        {
            /// <summary>Gets the zero-based depth at which the visitable entity resides.</summary>
            int Depth { get; }

            /// <summary>Gets or sets the number of times the visitable entity has been visited.</summary>
            int Visited { get; set; }
        }

        /// <summary>Provides the implementation for all visitable classes.</summary>
        /// <typeparam name="TDerived">See <see cref="Visitable{T, U}"/>.</typeparam>
        private class MyVisitable<TDerived> : Visitable<TDerived, IMyVisitable>, IMyVisitable
            where TDerived : MyVisitable<TDerived>
        {
            private int depth;
            private int visited;

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////

            /// <summary>See <see cref="IMyVisitable.Depth"/>.</summary>
            public int Depth
            {
                get { return this.depth; }
                set { this.depth = value; }
            }

            /// <summary>See <see cref="IMyVisitable.Visited"/>.</summary>
            public int Visited
            {
                get { return this.visited; }
                set { this.visited = value; }
            }
        }

        /// <summary>Concrete visitable class 1.</summary>
        private class MyVisitable1 : MyVisitable<MyVisitable1>
        {
        }

        /// <summary>Concrete visitable class 2.</summary>
        private class MyVisitable2 : MyVisitable<MyVisitable2>
        {
        }

        /// <summary>Concrete visitable class 3.</summary>
        private class MyVisitable3 : MyVisitable<MyVisitable3>
        {
        }

        /// <summary>Visitable class where the TDerived parameter is wrong.</summary>
        private class MyBadVisitable : MyVisitable<MyVisitable2>
        {
        }

        /// <summary>The base interface that needs to be implemented to visit a single type.</summary>
        /// <typeparam name="TVisitableDerived">See <see cref="IVisitor{T, U}"/>.</typeparam>
        private interface IMyVisitor<TVisitableDerived> : IVisitor<TVisitableDerived, IMyVisitable>
            where TVisitableDerived : IVisitable<TVisitableDerived, IMyVisitable>
        {
        }

        /// <summary>The interface that needs to be implemented to visit all types.</summary>
        private interface IMyCombinedVisitor : IMyVisitor<MyVisitable1>, IMyVisitor<MyVisitable2>
        {
        }

        /// <summary>The concrete visitor implementation.</summary>
        private class MyVisitor : IMyCombinedVisitor
        {
            /// <summary>See <see cref="IVisitor{T}.OnVisit"/>.</summary>
            [SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "The method is part of a non-public class.")]
            public void OnVisit(IMyVisitable visitable)
            {
                ++((MyVisitable3)visitable).Visited;
            }

            /// <summary>See <see cref="IVisitor{T, U}.OnVisit"/>.</summary>
            [SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "The method is part of a non-public class.")]
            public void OnVisit(MyVisitable1 visitable)
            {
                ++visitable.Visited;
            }

            /// <summary>See <see cref="IVisitor{T, U}.OnVisit"/>.</summary>
            [SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "The method is part of a non-public class.")]
            public void OnVisit(MyVisitable2 visitable)
            {
                ++visitable.Visited;
            }
        }
    }
}
